package osmedile.intellij.util.psi;

import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.PsiLiteralExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Olivier Smedile
 * @version $Id: PsiEquivalenceUti.java 15 2008-06-24 13:00:22Z osmedile $
 */
public class PsiEquivalenceUti {
    /**
     * Search if "eltToFind" is contained inside "content".
     * Comparaison is made on all childs psi elements.
     * Some elements can be excluded like comments.
     *
     * @param content
     * @param eltToFind              element to find inside content
     * @param eltComparator
     * @param allower                list of elements which are allowed to vary between content and eltToFind.
     * @param allowedVariantEltsPeer list of elements which have effectively varied from eltToFind.
     * @param includeComments        true if comments must be compared too
     *
     * @return top elements included in content and equals to eltToFind
     */
    public static PsiElement[] findContainedPsi(final @NotNull PsiElement content,
                                                final @NotNull PsiElement eltToFind,
                                                @Nullable Comparator<PsiElement> eltComparator,
                                                @NotNull Map<PsiElement, PsiElement> allowedVariantEltsPeer,
                                                @Nullable Allower allower,
                                                boolean includeComments) {
        if (content == eltToFind) {
            return new PsiElement[]{content};
        }
        ASTNode node1 = content.getNode();
        ASTNode node2 = eltToFind.getNode();
        if (node1 == null || node2 == null) {
            return new PsiElement[0];
        }
//        if (node1.getElementType() != node2.getElementType()) {
//            return new PsiElement[];
//        }

        if (eltToFind instanceof PsiReferenceExpression) {
            if (allower == null &&
                    content.getText().trim().equalsIgnoreCase(eltToFind.getText().trim()) ||
                    (allower != null && allower.allowVariant(content, eltToFind, allowedVariantEltsPeer))) {
                return new PsiElement[]{content};
            }
        } else if(eltToFind instanceof PsiLiteralExpression) {
            if (allower == null &&
                    content.getText().trim().equalsIgnoreCase(eltToFind.getText().trim()) ||
                    (allower != null && allower.allowVariant(content, eltToFind, allowedVariantEltsPeer))) {
                return new PsiElement[]{content};
            }
        }

        PsiElement[] children1 = getFilteredChildren(content, includeComments);
        PsiElement[] children2 = getFilteredChildren(eltToFind, includeComments);

        if (children2.length == 0) {
            if (allower == null &&
                    content.getText().trim().equalsIgnoreCase(eltToFind.getText().trim()) ||
                    (allower != null && allower.allowVariant(content, eltToFind, allowedVariantEltsPeer))) {
                return new PsiElement[]{content};
            }
            return null;
        } else if (children1.length >= children2.length) {
            //search if all children2 are equals to a subset of children 1
            Map<PsiElement, PsiElement> tempPeer = new HashMap<PsiElement, PsiElement>();
            final int decalage = children1.length - children2.length;
            for (int child1Idx = 0; child1Idx <= decalage; child1Idx++) {
                boolean good = true;
                tempPeer.clear();
                for (int child2Idx = 0; child2Idx < children2.length && good; child2Idx++) {
                    PsiElement[] result =
                            findContainedPsi(children1[child1Idx + child2Idx], children2[child2Idx],
                                    eltComparator, tempPeer, allower, includeComments);
                    if (result == null) {
                        good = false;
                    }
                }
                if (good) {
                    //all children are equals
                    allowedVariantEltsPeer.putAll(tempPeer);

                    PsiElement[] result = new PsiElement[children2.length];
                    System.arraycopy(children1, child1Idx, result, 0, children2.length);
                    return result;
                }
            }
        }

        //go deeper

        //search if a children is equals to eltToFind
        for (PsiElement child : children1) {
            Map<PsiElement, PsiElement> tempPeer = new HashMap<PsiElement, PsiElement>();
            PsiElement[] result = findContainedPsi(child, eltToFind, eltComparator, tempPeer,
                    allower, includeComments);
            if (result != null) {
                allowedVariantEltsPeer.putAll(tempPeer);
                return result;
            }
        }
        return null;

//
//        PsiReference ref1 = content.getReference();
//        if (ref1 != null) {
//            PsiReference ref2 = eltToFind.getReference();
//            if (ref2 == null) {
//                return false;
//            }
//            PsiElement resolved1 = ref1.resolve();
//            PsiElement resolved2 = ref2.resolve();
//            if (!Comparing.equal(resolved1, resolved2) && (eltComparator == null ||
//                    eltComparator.compare(resolved1, resolved2) != 0)) {
//                return false;
//            }
//        }
//        return true;

    }

    public static PsiElement[] getFilteredChildren(PsiElement element1, boolean includeComments) {
        ASTNode[] children1 = element1.getNode().getChildren(null);
        ArrayList<PsiElement> array = new ArrayList<PsiElement>();
        for (ASTNode node : children1) {
            final PsiElement child = node.getPsi();
            if (!(child instanceof PsiWhiteSpace) && (includeComments ||
                    !(child instanceof PsiComment))) {

                //Modif from IDEA
                //remove "*" text
                if (!"*".equals(child.getText().trim())) {
                    array.add(child);
                }
            }
        }
        return array.toArray(new PsiElement[array.size()]);
    }
}
